<?php

declare(strict_types=1);

namespace Tools\Command;

use Tools\Command\Drive\AntiArcher;
use Tools\Command\Drive\Execute;
use Tools\Command\Drive\Passthru;
use Tools\Command\Drive\POpen;
use Tools\Command\Drive\Process;
use Tools\Command\Drive\ShellExecute;
use Tools\Command\Drive\System;

/**
 * Class Command
 * 
 * @final
 * @package Tools\Command
 */
final class Command
{
    const EXECUTE = 1;           // exec
    const SYSTEM = 2;            // system
    const PASSTHRU = 3;          // passthru
    const POPEN = 4;             // popen
    const SHELL_EXCETUE = 5;     // shell_exec
    const ANTI_ARCHER = 7;       // ``

    /**
     * Input
     *
     * @var string
     */
    public static string $in = 'stdin';

    /**
     * Output
     *
     * @var string
     */
    public static string $out = 'stdout';

    /**
     * Error
     *
     * @var string
     */
    public static string $error = 'stderr';

    /**
     * Execute command
     *
     * @param string $command
     * @param array $args
     * @param mixed $result
     * @param integer $type
     * @return integer
     */
    public final static function execute(string $command, array $args, &$result, int $type = self::ANTI_ARCHER) : int
    {
        $status = 0;

        switch ($type) {
            case self::EXECUTE: 
                $handler = new Execute();
                $status = $handler->handle(self::builder($command, $args), $result);
                break;
            case self::SYSTEM: 
                $handler = new System();
                $status = $handler->handle(self::builder($command, $args), $result);
                break;
            case self::PASSTHRU: 
                $handler = new Passthru();
                $status = $handler->handle(self::builder($command, $args), $result);
                break;
            case self::POPEN:
                $handler = new POpen();
                $status = $handler->handle(self::builder($command, $args), $result);
                break;
            case self::SHELL_EXCETUE:
                $handler = new ShellExecute();
                $status = $handler->handle(self::builder($command, $args), $result);
                break;
            case self::ANTI_ARCHER:
                $handler = new AntiArcher();
                $status = $handler->handle(self::builder($command, $args), $result);
                break;
            default:
                throw new \Exception('Type not handle!');
        }

        return $status;
    }

    /**
     * Standard handle
     *
     * @param ?string|int $in
     * @param ?string|int $out
     * @param ?string|int $error
     * @return void
     */
    public final static function initialize(string $in = null, $out = null, $error = null)
    {
        if (!is_null($in)) self::$in = $in;
        if (!is_null($out)) self::$out = $out;
        if (!is_null($error)) self::$error = $error;
    }

    /**
     * Escape command
     *
     * @param string $command
     * @return string
     */
    public static function escapeCommand(string $command) : string
    {
        return escapeshellcmd($command);
    } 

    /**
     * Escape args
     *
     * @param array $args
     * @return array
     */
    public static function escapeArgs(array $args) : array
    {
        $result = [];

        foreach ($args as $item) {
            $result[] = escapeshellarg($item);
        }

        return $result;
    }

    /**
     * Builde command
     *
     * @param string $command
     * @param array $args
     * @param boolean $isQuote
     * @return string
     */
    public static function builder(string $command, array $args, $isQuote = true) : string
    {
        $result = '';

        $result .= self::escapeCommand($command);
        $escapeArgs = self::escapeArgs($args);
        foreach ($escapeArgs as $argument) {
            $result .= " {$argument}";
        }

        // stream redirect.
        if ($isQuote) {
            if (self::$in !== 'stdin') $result .= (' 0>' . self::buildQuote(self::$in));
            if (self::$out !== 'stdout') $result .= (' 1>' . self::buildQuote(self::$out));
            if (self::$error !== 'stderr') $result .= (' 2>' . self::buildQuote(self::$error));
        }

        return $result;
    }

    /**
     * Quote
     *
     * @param integer|string $value
     * @return void
     */
    public static function buildQuote($value) 
    {
        if (is_numeric($value)) $value = (int) $value;

        return in_array($value, [0, 1, 2]) ? "&{$value}" : $value;
    }
}